/* Snippet e-commerce Se encarga del rendereo de información para los sitios de e-commerce. - Carga de link informativo de atrato. - Carga de link para pago de atrato. - Comportamiento independiente para dispositivos móviles y de escritorio. */ const LogoAtratoSVGLabel = ` `; const LogoAtratoSVGBtn = ` `; window.Atrato_Pago_Events = class AtratoPagoEventHandlers { constructor(body) { if (body) { const { onSuccess, onPending, onRejected, onError } = body; this.onSuccess = onSuccess; this.onPending = onPending; this.onRejected = onRejected; this.onError = onError; window.addEventListener( 'message', (event) => { if (event.data === 'ATRATO_CLOSED') { this.isQuoteComplete(); } if (event.data === 'ATRATO_COMPLETE') { this.isQuoteComplete(); } }, false ); } } isQuoteComplete() { // Validar si estamos en el checkout let divAtratoPago = document.getElementById('higslide-div-atrato-info'); //Sino existe if (divAtratoPago === null) { return; } //Sino estamos en el pago if (modalType !== 'pago') { return; } const quoteId = divAtratoPago.dataset.orderid; const idCommerce = divAtratoPago.dataset.idcommerce; const encodedDataKeys = divAtratoPago.dataset.key; let sandbox = false; let divsandbox = divAtratoPago.dataset.sandbox; if (divsandbox) { if (typeof divsandbox === 'string') { if (divsandbox.toLowerCase() === 'true') { sandbox = true; } } if (typeof divsandbox === 'boolean') { sandbox = divsandbox; } } let urlApiatrato = sandbox ? 'https://api-sandbox.atratopago.com' : 'https://api.atratopago.com'; var requestOptions = { method: 'GET', redirect: 'follow', }; // Se realiza un request al siguiente endpoint para saber el estatus de la solicitud. fetch( `${urlApiatrato}/ecommerce/custom/v2/quote/${quoteId}/${encodedDataKeys}/${idCommerce}/completed`, requestOptions ) .then((response) => response.text()) .then((result) => { const objResult = JSON.parse(result); if (objResult.success) { if (objResult.step.status === 'complete') { if (this.onSuccess) { this.onSuccess(objResult); } } if (objResult.step.status === 'pending') { if (this.onPending) { this.onPending(objResult); } } if (objResult.step.status === 'rejected') { if (this.onRejected) { this.onRejected(objResult); } } } else { if (this.onPending) { this.onPending(objResult); } } }) .catch((error) => { if (this.onError) { this.onError(error); } }); } getPrecio({ precio, idcommerce, mensualidades, sandbox = false }) { const requestOptions = { method: 'GET', redirect: 'follow', }; let urlApiatrato = 'https://api.atratopago.com'; const urlEndpointData = `${urlApiatrato}/api/v1/partners/precio/${idcommerce}/${mensualidades}/${precio}`; return new Promise((resolve, reject) => { fetch(urlEndpointData, requestOptions) .then((response) => { return response.json(); }) .then((objResult) => { resolve(objResult); }) .catch((error) => { reject(error); }); }); } }; let urlHostStatictContent = ''; let urlhost = ''; let modalType = ''; let urlInfo = ''; let urlPago = ''; let urlDocumentos = ''; const getPluginHost = (environment) => { const PLUGIN_SCRIPT_HOSTS_ENVS = { local: 'http://localhost:3000', dev: 'https://app-beta.atratotech.com', beta: 'https://app-beta.atratotech.com', staging: 'https://atratotech.com', prod: 'https://atratopago.com', custom: 'add here a custom domain such an ngrok', }; const defaultHostKey = 'prod'; // When a data-env is not provided, this is going to be assgined if (!environment) { console.warn( `Plugin script host environment not provided. Defaulting to ${defaultHostKey}` ); return PLUGIN_SCRIPT_HOSTS_ENVS[defaultHostKey]; } const hostEnv = PLUGIN_SCRIPT_HOSTS_ENVS[environment]; if (!hostEnv) { `The provided plugin script host environment was not found. Defaulting to ${defaultHostKey}`; return PLUGIN_SCRIPT_HOSTS_ENVS[defaultHostKey]; } return hostEnv; }; const oldVersionFormat = { '1.0': 'v1', '2.0': 'v2', '3.0': 'v3', '4.0': 'v4', }; /** Se cargan variables de entorno * Se definen las urls a las cuales van a apuntar los metodos de las demás funciones * Solo se puede cargar un lebel con id */ const loadVarEnv = () => { let divAtratoInfo = document.getElementById('higslide-div-atrato-info'); // V4 PLUGIN INJECTION const script = document.createElement('script'); script.src = `${getPluginHost( divAtratoInfo.dataset.env )}/ecommerce_plugin/v4/atratoInfoPlugin.js`; script.id = 'atratoPluginScript'; script.dataset.version = oldVersionFormat[divAtratoInfo.dataset.version]; script.dataset.env = divAtratoInfo.dataset.env; // Append to ecommerce's site document.head.appendChild(script); if (divAtratoInfo === null) { return; } urlHostStatictContent = divAtratoInfo.dataset.urlhost; modalType = divAtratoInfo.dataset.type; urlInfo = `${urlHostStatictContent}/ecommerce/masInfo`; urlPago = `${urlHostStatictContent}/ecommerce/checkout`; urlDocumentos = `${urlHostStatictContent}/v3/accounts`; hs.graphicsDir = `${urlHostStatictContent}/ecommerce_plugin/graphics/`; // hs.outlineType = 'rounded-white'; hs.wrapperClassName = 'draggable-header'; // hs.align = 'center'; }; /** Variables de entorno para varias labels * Se guardna las mismas variables de estado para n cantidad de labels */ const loadVarEnvByClass = () => { var els = document.getElementsByClassName('higslide-div-atrato-info'); if (els.length === 0) { return; } Array.prototype.forEach.call(els, function (el) { urlHostStatictContent = el.dataset.urlhost; modalType = el.dataset.type; urlInfo = `${urlHostStatictContent}/ecommerce/masInfo`; urlPago = `${urlHostStatictContent}/ecommerce/checkout`; urlDocumentos = `${urlHostStatictContent}/v3/accounts`; hs.graphicsDir = `${urlHostStatictContent}/ecommerce_plugin/graphics/`; // hs.outlineType = 'rounded-white'; hs.wrapperClassName = 'draggable-header'; // hs.align = 'center'; addbtnAtratoInfo(el); }); }; // Valida si es un navegador mobile window.mobileCheck = function () { let check = false; (function (a) { if ( /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test( a ) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test( a.substr(0, 4) ) ) check = true; })(navigator.userAgent || navigator.vendor || window.opera); return check; }; function utf8_to_b64(str) { return window.btoa(unescape(encodeURIComponent(str))); } function b64_to_utf8(str) { return decodeURIComponent(escape(window.atob(str))); } function ConvertStringToHex(str) { let arr = []; for (let i = 0; i < str.length; i++) { arr[i] = ('00' + str.charCodeAt(i).toString(16)).slice(-4); } return '\\u' + arr.join('\\u'); } function ConvertHexToString(str1) { let hex = str1.toString(); let str = ''; for (let n = 0; n < hex.length; n += 2) { str += String.fromCharCode(parseInt(hex.substr(n, 2), 16)); } return str; } const pagoNeto = (precioProducto) => { return ( (0.35 * 1.16 * precioProducto) / 12 / (1 - (1 + (0.35 * 1.16) / 12) ** -18) ); }; const pagoNetoCustom = (precioProducto, mensualidad, interes = 0.35) => { if (interes === 0) { return precioProducto / mensualidad; } return ( (interes * 1.16 * precioProducto) / 12 / (1 - (1 + (interes * 1.16) / 12) ** -mensualidad) ); }; const currencyFormatToString = ( amount, decimalCount = 0, decimal = '.', thousands = ',' ) => { let decimalCountVar = Math.abs(decimalCount); decimalCountVar = Number.isNaN(decimalCountVar) ? 2 : decimalCountVar; const negativeSign = amount < 0 ? '-' : ''; let amountVar; const i = parseInt( (amountVar = Math.abs(Number(amount) || 0).toFixed(decimalCountVar)), 10 ).toString(); const j = i.length > 3 ? i.length % 3 : 0; return ( negativeSign + (j ? i.substr(0, j) + thousands : '') + i.substr(j).replace(/(\d{3})(?=\d)/g, `$1${thousands}`) + (decimalCountVar ? decimal + Math.abs(amountVar - i) .toFixed(decimalCountVar) .slice(2) : '') ); }; const validarParcialidades = (dataPartials) => { let returnValue = true; /** Convertir a array */ const dataArray = dataPartials.split(','); /** Tiene que ser de longitud exacta = 3 */ if (dataArray.length != 3) { returnValue = false; } let temp = 0; /** Recorrer array */ dataArray.map((x) => { /** Convertir a entero */ const item = parseInt(x); /** Validar si es un entero válido */ if (!Number.isInteger(item)) { returnValue = false; } /** Validar si el valor es mayor que 60 mensualidades */ if (item > 60) { returnValue = false; } /** Si es entero, validar que sea mayor que el anterior y mayor que cero */ if (returnValue) { returnValue = temp < item; } temp = item; }); return returnValue; }; const ObserverFaction = (divAtratoInfo) => { const observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { if (mutation.type === 'attributes') { console.log('Change attributes'); loadVarEnv(); loadVarEnvByClass(); loadModal(); } }); }); observer.observe(divAtratoInfo, { attributes: true, //configure it to listen to attribute changes }); }; /** Carga las preferencias del usuario * Se pasa el nodo que contega la class higslide-div-atrato-info * Deault div con el id higslide-div-atrato-info * Todas las propiedades definidas dentro del data-{property} * @param divAtratoInfo HTMLElement */ const addbtnAtratoInfo = ( divAtratoInfo = document.getElementById('higslide-div-atrato-info') ) => { if (divAtratoInfo === null) { return; } let price = 0; // Propiedades de la v2 let linkColor = 'black'; let modalStyle = 'light'; let versionLogoLink = 'black'; ObserverFaction(divAtratoInfo); // propiedades de la v3 let font; let colorAtrato; let size; let sizePrice; /* En caso de ser de la plataforma de Shopify */ if (divAtratoInfo.dataset.plataform === 'shopify') { // Validar que traiga precio if (!divAtratoInfo.dataset.price) { // Si no trae, acceder a la clase de Anailitcs de shopify para extraer el precio. /* eslint-disable-next-line */ try { const productData = ShopifyAnalytics.meta.product.variants[0]; if (productData.price) { price = productData.price; } } catch (e) { console.error(e); price = undefined; } } else { price = parseFloat(divAtratoInfo.dataset.price); } // Se divide entre 100 porque shopify retorna el monto sin decimales. // ej. 90000 = $900.00 price = price / 100; } /* En caso de ser de la plataforma de Magento */ if (divAtratoInfo.dataset.plataform === 'magento') { // Validar que traiga precio if (!divAtratoInfo.dataset.price) { // Si no trae, acceder a la clase de localStorage de magento para extraer el precio. const jsonDatajson = JSON.parse(localStorage.product_data_storage); const productDataId = Object.keys(jsonDatajson)[0]; if (jsonDatajson[productDataId]) { const Product = jsonDatajson[productDataId].price_info; if (Product) { price = Product.final_price; } } } else { price = parseFloat(divAtratoInfo.dataset.price); } } /* En caso de ser de cualquier otra plataforma */ if (divAtratoInfo.dataset.price && price === 0) { price = parseFloat(divAtratoInfo.dataset.price); } const priceUpperBound = 200000; const priceLowerBount = 1000; if (price < 1000 || price > 200000) { console.warn( `ATRATO: El precio del producto ($${price}) está fuera del rango de $1000 a $200000` ); return; } if (divAtratoInfo.dataset.linkcolor) { // Color del link // Pinta toda el div en la v3 y solo existen 3 variantes en la v2 linkColor = divAtratoInfo.dataset.linkcolor; switch (divAtratoInfo.dataset.linkcolor) { case 'black': versionLogoLink = divAtratoInfo.dataset.linkcolor; break; case 'white': versionLogoLink = divAtratoInfo.dataset.linkcolor; break; default: versionLogoLink = 'colores'; } } // Estilo de la modal V2 if (divAtratoInfo.dataset.style) { modalStyle = divAtratoInfo.dataset.style; if ( modalStyle !== 'Dark' && modalStyle !== 'dark' && modalStyle !== 'light' ) { modalStyle = 'light'; } } // Armar url para la versión de la modal. // urlInfo default trae la versión 1.0: /ecommerce/masInfo if (divAtratoInfo.dataset.version) { switch (divAtratoInfo.dataset.version) { case '1.0': urlInfo = `${urlHostStatictContent}/ecommerce/masInfo?`; break; case '2.0': urlInfo = `${urlInfo}V2?precio=${price}&mode=${modalStyle}`; break; case '3.0': urlInfo = `${urlInfo}V3?precio=${price}&mode=${modalStyle}`; break; default: urlInfo = `${urlInfo}V2?precio=${price}&mode=${modalStyle}`; } } else { // Sinó trae versión poner la modal de la calculadora Versión 2. urlInfo = `${urlInfo}V2?precio=${price}&mode=${modalStyle}`; } // Personalización de los modales // Todas las propiedades aceptan propiedades de css // Pita los svgs dentro de los modales de la v3 // encode URI para parsear los # dentro de los colores hex en una URI if (divAtratoInfo.dataset.icons) { urlInfo = `${urlInfo}&icons=${encodeURIComponent( divAtratoInfo.dataset.icons )}`; } // Cambia el bg dentro de los modales de v3 if (divAtratoInfo.dataset.background) { urlInfo = `${urlInfo}&bg=${encodeURIComponent( divAtratoInfo.dataset.background )}`; } // Cambia los titulos dentro de los modales if (divAtratoInfo.dataset.heading) { urlInfo = `${urlInfo}&heading=${encodeURIComponent( divAtratoInfo.dataset.heading )}`; } // Cambio de los textos en los textos en modales de V3 if (divAtratoInfo.dataset.text) { urlInfo = `${urlInfo}&txt=${encodeURIComponent( divAtratoInfo.dataset.text )}`; } // Cambia la font family dentro de los modales de v3 if (divAtratoInfo.dataset.font) { urlInfo = `${urlInfo}&font=${divAtratoInfo.dataset.font}`; } // Link que pasan los partners para obtener más información dentro de los modales de v3 if (divAtratoInfo.dataset.buttonlink) { urlInfo = `${urlInfo}&url=${encodeURIComponent( divAtratoInfo.dataset.buttonlink )}`; } // Personalización del font family del label de información // Limitado a que el font este presente en el DOM del partner // Default Helvetica if (divAtratoInfo.dataset.fontlabel) { font = divAtratoInfo.dataset.fontlabel; } // Cambia de color el logo de atrato en el label de información // Acepta cualquier propiedad de css el // default es el degradado if (divAtratoInfo.dataset.atrato) { colorAtrato = divAtratoInfo.dataset.atrato; } // Cambia el tamaño la label de información // Acepta cualquier medida de cssd default 12pt if (divAtratoInfo.dataset.sizelabel) { size = divAtratoInfo.dataset.sizelabel; } // El tag que muestra el precio es de otra medida para resaltar dentro de la pagina del partner // Defualt 1 rem if (divAtratoInfo.dataset.sizeprice) { sizePrice = divAtratoInfo.dataset.sizeprice; } // Verificar atributo de parcialidades if (divAtratoInfo.dataset.parcialidad) { if (validarParcialidades(divAtratoInfo.dataset.parcialidad)) { const partials = divAtratoInfo.dataset.parcialidad; urlInfo = `${urlInfo}&partial=${partials}`; } } // Logo de comercios en modales v3 if (divAtratoInfo.dataset.logo) { const logo = divAtratoInfo.dataset.logo; if (typeof logo === 'string' && logo.includes('https://')) { urlInfo = `${urlInfo}&logo=${encodeURIComponent(logo)}`; } } // Imagen para el modal 4 V3 if (divAtratoInfo.dataset.image) { const image = divAtratoInfo.dataset.image; if (typeof image === 'string' && image.includes('https://')) { urlInfo = `${urlInfo}&image=${encodeURIComponent(image)}`; } } // Token para modificar el interes if (divAtratoInfo.dataset.token) { urlInfo = `${urlInfo}&token=${encodeURIComponent( divAtratoInfo.dataset.token )}`; } // Dimensiones para los modales V1 y V2 // Si es dispositivo móvil tomar el siguiente ancho: hs.height = 820; hs.width = 350; if (!mobileCheck()) { // Si no es móvil toma en alcho configurado abajo: hs.width = 480; hs.height = 790; } if (divAtratoInfo.dataset.version === '3.0') { const typeModal = divAtratoInfo.dataset.modal; // Todos lo modales tinene diferentes tamaños // La unica restricción es que para telefonos se tiene que tener un ancho de 350px // El largo es opcional // El modal por default es el 5 switch (typeModal) { case '1': hs.width = 560; hs.height = 550; if (mobileCheck()) { hs.width = 350; hs.height = 700; } break; case '2': hs.width = 480; hs.height = 550; if (mobileCheck()) { hs.width = 350; hs.height = 520; } break; case '3': hs.width = 650; hs.height = 510; if (mobileCheck()) { hs.width = 350; hs.height = 670; } break; case '4': hs.width = 751; hs.height = 500; if (mobileCheck()) { hs.width = 350; hs.height = 820; } break; default: case '5': hs.height = 536; hs.width = 600; if (mobileCheck()) { hs.width = 350; hs.height = 650; } break; } urlInfo = `${urlInfo}&modal=${parseInt(typeModal || 5)}`; } // Se obtiene el pago por las mensualidades proporcionadas por el partner let montoMensual = Math.round(pagoNeto(price)); if (divAtratoInfo.dataset.parcialidad) { if (validarParcialidades(divAtratoInfo.dataset.parcialidad)) { const dataArray = divAtratoInfo.dataset.parcialidad.split(','); const mensualidadTop = parseInt(dataArray[2], 10); if (mensualidadTop >= 9) { montoMensual = Math.round(pagoNetoCustom(price, mensualidadTop)); } if (mensualidadTop >= 36) { montoMensual = Math.round(pagoNetoCustom(price, mensualidadTop, 0)); } } } // Se asignan formato de string al precio const montoString = currencyFormatToString(montoMensual); // Leyenda de promoción con atrato let leyenda = ''; const priceLabelStyle = sizePrice ? `font-size: ${sizePrice}` : 'font-size:0.8rem'; if (!montoMensual || montoMensual === 0 || montoMensual < 0) { leyenda = `Págalo a meses con crédito `; } else { leyenda = `Desde $${montoString} al mes con crédito `; } // carga los styles por default const styles = `color: ${linkColor};` + (font ? `font-family:${font};` : 'font-family: Verdana, Helvetica;') + (size ? `font-size: ${size};` : `font-size: 10pt;`) + 'font-weight: initial;' + 'text-transform: initial;'; let fillSVG = colorAtrato || 'url(#paint0_linear)'; // En la v2 y v1 se usaban las imagenes, por lo que el link color blue da como resultado if (divAtratoInfo.dataset.version !== '3.0') { switch (linkColor) { case 'black': fillSVG = 'black'; break; case 'white': fillSVG = 'white'; break; default: fillSVG = 'url(#paint0_linear)'; break; } } let stylesLogoSVG = 'width: initial; fill: unset;' + 'vertical-align: bottom;' + (size ? `height: ${size};` : `height: 11pt;`); const pluginVersion = divAtratoInfo.getAttribute('data-pluginVersion'); let dataAttributes = ''; for (data in divAtratoInfo.dataset) { // Price may go through some transformations, so we are going to overwrite it to apply said transformations if (data === 'price') { dataAttributes += ` data-${data}=${price} `; if (montoMensual) { dataAttributes += ` data-pricePreview=${montoMensual} `; } continue; } dataAttributes += ` data-${data}=${divAtratoInfo.dataset[data]} `; } // To ensure no information is lost, send all url params generated to v4 plugin const legacyURL = new URL(urlInfo); dataAttributes += ` data-iframeParams=${legacyURL.search} `; // Legacy plugin raw html tag const innerHTMLLegacy = `